<?php
/**
 * Модель квестов - расширенный итем.
 *
 * @package default
 * @author Konstantin Vdovkin Vasiliy Miljaev
 **/
$this->useClass("Model_Item");
class Model_Quest extends Model_Item {
	const STATE_DENITED = 3;
	const STATE_ALLOW = 4;
	const STATE_INQUEST = 0;
	const STATE_INFINAL = 1;
	const STATE_INBAN = 2;

	const ROLE_GIVER 	= 1;
	const ROLE_AUTOR 	= 2;
	const ROLE_VOTER 	= 3;
	const ROLE_DOCAUTOR = 4;

	const VM_BINAR		= 1;
	const VM_SCALE		= 2;
	const VM_CATEGORY	= 3;
	const VM_CATEGORIES = 4;
	const VM_TAGS		= 5;
	const VM_COMMENT	= 6;

	public function createItem($data=null) {
		$fquest = parent::createItem("quest");

		if($data==null) {
			//создание нового квеста
			$info = $this->mm->aa->getInfo();

			$fquest->data['community_oid'] = $info['comity_oid'];
			$fquest->data['posted_by_id'] = $info['user_id'];
			$fquest->data['create_date'] = date("Y-m-d");

			$fquest->data['user_giver'] = array($userId);
			//массивы, кот нужно инициализировать
			$initArrays = array(
				'user_giver','user_autor','user_voter','user_docautor',
				'userban_giver','userban_autor','userban_voter','userban_docautor',
				'doc_dates','votes','doc_tdi'
			);
			foreach ($initArrays as $arr) {
				$fquest->data[$arr] = array();
			}
		} else {
			//восстновление из сессии недоделанного квеста
			$fquest->data = $data;
		}

		return $fquest;
	}

	public function getActiveQuests() {
		$cid = $this->mm->aa->acl_data['comity_oid'];
		$uid = intval($this->mm->aa->acl_data['user_id']);
		$prefix = $this->db->db_prefix;

        $sql = "SELECT o.*
						FROM {$prefix}objects o INNER JOIN {$prefix}class_quest q USING(oid)
            LEFT JOIN {$prefix}class_allfeed a USING(oid)
            WHERE q.community_oid={$cid}
						AND (q.is_published=1 OR a.posted_by_id={$uid})
						AND o.is_deleted=0
			";
			$rs = $this->store->findSQL($sql);

			$os = array();
			while($o = $rs->next()) {
				$o->data['oid'] = $o->id;
				$os[] = $o->data;
			}
			return $os;

    }

	public function getRightIndex($role, $isGroup, $isBan) {
		$roleNames = array(
			self::ROLE_GIVER => 'giver',
			self::ROLE_AUTOR => 'autor',
			self::ROLE_VOTER => 'voter',
			self::ROLE_DOCAUTOR => 'docautor'
		);

		return ($isGroup?"group":"user").
			($isBan?"ban_":"_").
			$roleNames[$role];
	}

	/**
	 * Назначение прав пользователю или группе
	 *
	 * @param fobject $oQuest
	 * @param Int $id
	 * @param Int $role
	 * @param Boolean $ban
	 * @param Boolean $group
	 */
	public function setRole($oQuest, $id, $role, $ban, $group){
		$index1 = $this->getRightIndex($role,$group,$ban);
		$index2 = $this->getRightIndex($role,$group,!$ban);

		$ret = false;

		if(!in_array($id, $oQuest->data[$index1]))
		{
			$gbi = array_search($id, $oQuest->data[$index2]);
			if($gbi!==false){
				//если юзер находится в противоположной групе - удалить его от туда
				//и вернуть true
				array_splice($oQuest->data[$index2],$gbi,1);
				$ret = true;
			}
			$oQuest->data[$index1][] = $id;
		}

		return $ret;
	}

	public function unsetRole($oQuest, $id, $role, $ban, $group) {
		$key = $this->getRightIndex($role,$group,$ban);
		$index = array_search($id,$oQuest->data[$key]);
		if($index !== false)
			array_splice($oQuest->data[$key],$index,1);
	}

	/**
	 * Проверка юзера на приверженность к той или иной квестовой группе
	 *	ToDo нужна проверка юзверя при работе с докумментом относительно квеста,
	 * 	короче чтобы в бан низя было левому запихать докуммент
	 *
	 * @param fobject $oQuest
	 * @param Int $uid
	 * @param Int $uroleid
	 * @param String $group
	 * @return boolean
	 */
	private function checkRole($oQuest, $uid, $uroleid, $group){
		//создателю квеста можно всегда быть гивером
		if($uid==$oQuest->data['posted_by_id'] && $group=='giver')
			return true;

		if(in_array($uid,$oQuest->data['userban_'.$group])){
			return false;
		}
		if(in_array($uid,$oQuest->data['user_'.$group])){
			return true;
		}
		if(($uroleid <= $oQuest->data['groupban_'.$group])&&($oQuest->data['groupban_'.$group]!=0)){
			return false;
		}
		if(($uroleid <= $oQuest->data['group_'.$group])&&($oQuest->data['group_'.$group]!=0)){
			return true;
		}
		return false;
	}

	/**
	 * Определение текущих прав пользователя внутри квеста
	 *
	 * @param fobject $oQuest
	 * @return Array
	 */
	public function getUserRole($oQuest) {
		$this->mm->aa->getInfo();
		$uid = $this->mm->aa->acl_data['user_id'];
		$uroleid = $this->mm->aa->acl_data['role']['oid'];

		$result[self::ROLE_GIVER] = $this->checkRole($oQuest, $uid, $uroleid, 'giver');
		$result[self::ROLE_AUTOR] = $this->checkRole($oQuest, $uid, $uroleid, 'autor');
		$result[self::ROLE_VOTER] = $this->checkRole($oQuest, $uid, $uroleid, 'voter');
		return $result;
	}

	private function checkDocument($oQuest, $oDoc) {
		//ограничения на документ хранятся серриализовано
		//$oQuest->data['doc_types'] = array('doc','image',..)
		//$oQuest->data['doc_dates'] = array('нижняя дата', 'верхняя дата'); (м/б NULL)
		if(!in_array($oDoc->data['tdi_oid'],$oQuest->data['doc_tdi'])){
			return false;
		}
		if($oQuest->data['doc_types'] !== null &&
				!in_array(substr($oDoc->type,1),$oQuest->data['doc_types']))
			return false;

		$date = $oQuest->data['doc_dates'][0];
		$ctime = $oDoc->ctime!==null?$oDoc->ctime:$oDoc->data['ctime']->format("%Y-%m-%d");
		if($date !== null && strcmp($date,$ctime) > 0 )
			return false;
		$date = $oQuest->data['doc_dates'][1];
		if($date !== null && strcmp($date,$ctime) < 0 )
			return false;

		if(in_array($oDoc->data['posted_by_id'], $oQuest->data['doc_autorbans']))
			return false;

		return true;
	}

	public function getDocumentState($oQuest, $oDoc,$dbState=false) {
		//проверяем документ в любом случае, даже если есть связь в явном виде в БД
		// (могли измениться условия)
		if(!$this->checkDocument($oQuest, $oDoc)) {
			return self::STATE_DENITED;
		}
		//$dbState : null - нет в БД, false - неизвестно
		if($dbState !== false && $dbState !== null)
			return $dbState;

		if($dbState === false) {
			//запрос в базу
			$sql = "SELECT state FROM ".$this->db->db_prefix."class_quest_object".
				" WHERE quest_oid=".$oQuest->id." AND object_oid=".$oDoc->id;
			$rs = $this->store->query($sql);

			if($item = $rs->next()) {
				return $item['state'];
			}
		}
		return self::STATE_ALLOW;
	}
	/**
	* Действия, кот можно приизвести над докуметом
	* Возвращает массив
	* array(
	* 	'add'	=> true|null,
	* 	'ban'	=> true|null,
	* 	'unban'	=> true|null,
	* 	'final'	=> true|null,
	* 	'vote'	=> true|null,
	* 	'cur_votes'	=> true|null,
	* 	'state'	=> self::STATE::... (текущее состояние в квесте)
	* )
	**/
	public function getDocumentActions($oQuest, $oDoc, $dbState=false) {
		$state = $this->getDocumentState($oQuest,$oDoc,$dbState);
		if($state == self::STATE_DENITED)
			return;
		$roles = $this->getUserRole($oQuest);

		$actions = array();
		//с фининировавним квестом делать ничего нельзя
        if(!$oQuest->data['is_finished']) {
    		//право принять документ
    		if( $state == self::STATE_ALLOW &&
    				($roles[self::ROLE_AUTOR] || $roles[self::ROLE_GIVER]) )
    			$actions['add'] = true;
    		//право разбанить (идентичен принять)
    		if( $state == self::STATE_INBAN && $roles[self::ROLE_GIVER])
    			$actions['unban'] = true;
    		//право забанить
    		if( $state == self::STATE_INQUEST && $roles[self::ROLE_GIVER])
    			$actions['ban'] = true;
    		//право принять в финал
    		if( $state == self::STATE_INQUEST && $roles[self::ROLE_GIVER] )
    			$actions['final'] = true;
    		//право голосовать
    		if( ($state == self::STATE_INQUEST || $state == self::STATE_ALLOW) ) {
					if( $roles[self::ROLE_VOTER] ) {
						//за себя голосовать нельзя
						$userId = $this->mm->aa->acl_data['user_id'];
						if($oDoc->data['posted_by_id'] != $userId) {
							// право голосовать
							$actions['vote'] = true;

							// возможность голосовать
							$actions['vote_ok'] = $actions['vote']
								&& (bool)$this->getQuestUnvotedVotes($oQuest, $oDoc->id);

						} else {
							// право называться постером документа
							$actions['poster'] = true;
						}
					} else {
							// право иметь неголосовательную роль
							$actions['voteless'] = true;
					}
    		}
    	}
		//право смотреть текущие оценки
		if($state==self::STATE_INQUEST || $state==self::STATE_INFINAL)
			$actions['cur_votes'] = true;

		$actions['state'] = $state;
		return $actions;
	}

	public function setDocumentState($questId, $docId, $state) {
	   if($state !== null) {
            $sql = "REPLACE ".$this->db->db_prefix."class_quest_object".
                " SET quest_oid=".intval($questId).", object_oid=".intval($docId).
                " , state=".$state;
        } else {
            $sql = "DELETE FROM ".$this->db->db_prefix."class_quest_object".
                " WHERE quest_oid=".intval($questId)." AND object_oid=".intval($docId);
        }
        $this->store->query($sql);

		$this->documentStates[$questId][$docId] = $state;
	}

	/**
	*	Функия добавляет номинацию голосования
	*   на входе массив вида
	*   array('name'=>'Название номинации', 'method'=>self::VM_...,
	*         [optional]'variants'=>array(),
	* 		  [optional]'description' => 'Описание номинации'
	*    )
	**/
	public function addVote($oQuest, $voteParams) {
		$voteId = 1;
		$votes = count($oQuest->data['votes']);
		for($i=0; $i<$votes; $i++) {
			$id = $oQuest->data['votes'][$i]['vote_id'];
			if($id >= $voteId)
				$voteId = $id+1;
		}
		$voteParams['vote_id'] = $voteId;
		$oQuest->data['votes'][] = $voteParams;

		return $voteId;
	}

	public function getVoteIndex($oQuest, $voteId) {
		$votes = count($oQuest->data['votes']);
		$index = null;
		for($i=0; $i<$votes; $i++) {
			if($voteId == $oQuest->data['votes'][$i]['vote_id']) {
				$index = $i;
				break;
			}
		}
		return $index;
	}

	public function removeVote($oQuest, $voteId) {
		$index = $this->getVoteIndex($oQuest, $voteId);
		if($index !== null) {
			array_splice($oQuest->data['votes'],$index,1);
		}
		//удалим все голоса в номинации
		if($oQuest->id) {
			$sql = "DELETE FROM ".$this->db->db_prefix."class_quest_votes"
				." WHERE quest_oid=".$oQuest->id." AND vote_id=".intval($voteId);
			$this->store->query($sql);
		}
	}

	public function changeVote($oQuest, $voteId, $voteParams) {
		$index = $this->getVoteIndex($oQuest, $voteId);
		if($index !== null) {
			$voteParams['vote_id'] = $voteId;
			$oQuest->data['votes'][$index] = $voteParams;
		}
	}
	/*
	* Изменение порядка следования номинаций
	*/
	public function moveVote($oQuest, $voteId, $isUp) {
		$index = $this->getVoteIndex($oQuest, $voteId);
		if($index !== null) {
			$index1 = $index + ($isUp?-1:1);
			if($index1 < 0 || $index1 >= count($oQuest->data['votes']))
				return;

			$t = $oQuest->data['votes'][$index];
			$oQuest->data['votes'][$index] = $oQuest->data['votes'][$index1];
			$oQuest->data['votes'][$index1] = $t;
		}
	}

	public function getVotes($oQuest) {
		return $oQuest->data['votes'];
	}

	public function getVote($oQuest, $voteId) {
		$index = $this->getVoteIndex($oQuest, $voteId);
		return $oQuest->data['votes'][$index];
	}

	public function voteDoc($oDoc, $oQuest, $voteId, $vote) {
		$this->mm->aa->getInfo();
		$userId = $this->mm->aa->acl_data['user_id'];
		$votePar = $this->getVote($oQuest, $voteId);
		//баллы считаются только для голосований типа
		//"линейная шкала" и "бинарная шкала"
		// для остальных - 0
		$ball = 0;
		switch($votePar['method']){
			case self::VM_BINAR:
				$ball = $vote ? ($vote < 0 ? -1 : 1) : 0;
				break;
			case self::VM_SCALE:
				$ball = $vote < 0 ? 0 : ($vote > 10 ? 10 : $vote);
				break;
			case self::VM_CATEGORY:
				$vote = $votePar['variants'][$vote];
				break;
			case self::VM_CATEGORIES:
				$votes = explode(',', $vote);
					foreach($votes as $v){
					$votesa[] = $votePar['variants'][$v];
				}
				$vote = implode(",", $votesa);
				break;
			case self::VM_TAGS:
				break;
		}

		$sql = "INSERT INTO ".$this->db->db_prefix."class_quest_votes "
			."(quest_oid, vote_id, doc_oid, user_oid, vote, ball)"
			." VALUES "
			."(".$oQuest->id.",".$voteId.",".$oDoc->id.",".$userId.",'".$vote."',".$ball.")";
		$this->store->query($sql);
	}
	/**
	* Получение списка всех квестов, имеющих связь с данным документом
	**/
	public function getDocumentQuests($oDoc) {
		$prefix = $this->db->db_prefix;
		$sql = "SELECT q.oid
			, (SELECT COUNT(DISTINCT user_oid) AS voters
				 FROM {$prefix}class_quest_votes
				 WHERE doc_oid={$oDoc->id} AND quest_oid = q.oid
				) AS voters
			, o.state
			FROM {$prefix}class_quest q
			LEFT JOIN {$prefix}class_quest_object o
				ON (o.quest_oid=q.oid AND o.object_oid={$oDoc->id})
			WHERE q.community_oid = {$oDoc->data['community_oid']}
                AND q.is_published = 1";
		$rows = $this->db->query($sql);
		$quests = array();
		foreach($rows as $row) {
			$oQuest = $this->getItem($row['oid']);
			$actions = $this->getDocumentActions($oQuest, $oDoc, $row['state']);

			$quests[] = array(
				'id'	=> $oQuest->id,
				'quest'	=> $oQuest,
				'state'	=> $actions['state'],
				'actions'=> $actions,
				'voters'	=> intval($row['voters']),
			);
		}
		return $quests;
	}
	public function getVotersCount ($oQuest, $oDoc)
	{
		$prefix = $this->db->db_prefix;
		$sql = "SELECT COUNT(DISTINCT user_oid) AS voters
				 FROM {$prefix}class_quest_votes
				 WHERE doc_oid={$oDoc->id} AND quest_oid = {$oQuest->id}
				";
		$rows = $this->db->query($sql);
		return $rows ? $rows[0]['voters'] : NULL;
	} // end of method getVotersCount

	/**
	* Соберает все документы, учавствующие либо финалисты в квесте
	* $params = array(
	* 	'in_final'	=> true|false|null, //null - оба
	* 	'sort_by'	=> "balls"|"date"|null,
	* 	'voting_id'	=> (int)|null, 		//null - все номинации
	* 	'in_page'	=> (int)|null, 		//null - нет пейджера
	* 	'page'		=> (int),	 		//null=0, игнорируется если 'in_page'=null
	* );
	*
	**/
	public function getQuestDocuments(fobject $oQuest, $params) {
    	$this->mm->aa->acl_data['user_id'];
		//Формируем запрос
		$sql = "SELECT q.object_oid AS oid, q.state, b.balls
			 FROM ".$this->db->db_prefix."class_quest_object q
			 LEFT JOIN (
				SELECT SUM(ball) as balls, doc_oid
				FROM ".$this->db->db_prefix."class_quest_votes
				WHERE quest_oid=".$oQuest->id."
				".($params['voting_id']!==null?(" AND vote_id=".$params['voting_id']):"")."
				GROUP BY doc_oid
			) b ON q.object_oid=b.doc_oid";
		if($params['sort_by'] == "date") {
			$sql .= " INNER JOIN ".$this->db->db_prefix."objects o ON q.object_oid=o.oid";
		};
		$sql .= " WHERE q.quest_oid=".$oQuest->id;
		if($params['in_final'] !== null) {
			$sql .= " AND q.state=".
				($params['in_final']?self::STATE_INFINAL:self::STATE_INQUEST);
		} else
			$sql .= " AND q.state<>".self::STATE_INBAN;
		if($params['sort_by'] == "date") {
			$sql .= " ORDER BY o.mtime DESC";
		}
		if($params['sort_by'] == "balls") {
			$sql .= " ORDER BY b.balls DESC";
		}
		if($params['in_page'] !== null) {
			$sql .= " LIMIT ".($params['page']*$params['in_page']).",".$params['in_page'];
		}
		//получаем все иды и баллы
		$docOids = $this->db->query($sql);
		return $docOids;
	}

	public function getQuestDocumentsCount(fobject $oQuest, $params) {
		//Формируем запрос
		$sql = "SELECT COUNT(*) AS cnt
			 FROM ".$this->db->db_prefix."class_quest_object q
			 WHERE q.quest_oid=".$oQuest->id;
		if($params['in_final'] !== null) {
			$sql .= " AND q.state=".
				($params['in_final']?self::STATE_INFINAL:self::STATE_INQUEST);
		} else
			$sql .= " AND q.state<>".self::STATE_INBAN;
		//получаем количество документов
		$count = $this->db->query($sql);
		return $count[0]['cnt'];
	}
	/**
	* Балл конкретного пользователя в конкретной номинации
	**/
	public function getUserVote($vote, $questId, $docId) {
		$this->mm->aa->getInfo();
		$userId = $this->mm->aa->acl_data['user_id'];

		$sql = "SELECT vote_id, vote FROM ".$this->db->db_prefix."class_quest_votes"
			." WHERE quest_oid=".$questId." AND doc_oid=".$docId." AND user_oid=".$userId;
        if($vote != null)
            $sql .= " AND vote_id=".$vote['vote_id'];
		$res = $this->db->query($sql);
		if(count($res)) {
			if ($vote) return $res[0]['vote'];
			$r = array();
			foreach ($res as $k=>$v) {
				$r[$v['vote_id']] = $v;
			}
			return $r;
		}
	}

	public function getCurrentBalls($oQuest, $oDoc) {
		$sql = "SELECT vote_id,SUM(ball) AS summ, COUNT(*) AS count
			FROM ".$this->db->db_prefix."class_quest_votes
			WHERE quest_oid =".$oQuest->id."
			AND doc_oid =".$oDoc->id."
			GROUP BY vote_id";
		$res = $this->db->query($sql);
		$votes = array();
		$count = count($res);
		$votersCount = $summ = 0;
		for($i=0; $i<$count; $i++) {
			$votes[$res[$i]['vote_id']] = array(
				'count'	=> $res[$i]['count'],
				'summ'	=> $res[$i]['summ'],
				'mid'	=> $res[$i]['summ']/$res[$i]['count'],
			);
			//плохо, надо считать отдельно для каждой номинации
			if($res[$i]['count'] > $votersCount)
				$votersCount = $res[$i]['count'];
			$summ += $res[$i]['summ'];
		}

		return array(
			'votes'	=> $votes,
			'count' => $votersCount,
			'summ'	=> $summ,
		);
	}

	//URL для аякс-действий над документами
	public function getDocActionUrl($questId, $docId) {
		$this->mm->rh->useClass("RL_URLGenerator");
		$ug = new RL_URLGenerator($this->mm->rh->router->getRule("quest_doc_action"));
		$ug->setRouter($this->mm->rh->router);
		$ug->setParams(array(
			"community_url"	=> $this->mm->aa->acl_data['community_url'],
			"item_id"		=> $docId,
			"quest_id"		=> $questId,
		));
		return $ug->generateUrl()."/ajax";
	}

	public function getQuestActions($doc){
	   $oDoc = new fobject();
	   $oDoc->id = $doc['post']['oid'];
	   $oDoc->type = $doc['post']['type'];
	   $oDoc->data = $doc['post'];

		$quests = $this->getDocumentQuests($oDoc);
		$count = count($quests);
		$docActions = array();	//плюсики
		$counter = 0;
		$qs = array();

		foreach ($quests as $q) {

			$oQuest = $q['quest'];
			$actions = $q['actions'];
			$btnN = $counter%3;

			// если ниче делать нельзя -- следующий
			if (!($actions['cur_votes'] || $actions['add'] || $actions['vote'])) {
				continue;
			}

			// херим кол-во проголосовавших (суммарно), если нельзя смотреть
			if(!$actions['cur_votes']) {
				$q['_voters'] = $q['voters'];
				unset($q['voters']);
			}

			//плюсик показывается если: можно добавить, голосовать и смотреть текущие результаты
			if($actions['add'] || $actions['vote'] || $actions['cur_votes']) {
				$q['action_url'] = $this->getDocActionUrl($oQuest->id, $oDoc->id);
			}

			$q['btn_n'] = $btnN;
			$qs[] = $q;

			$counter++;
		}

		$res = array(
			'items' => $qs,
			// один квест и одна номинация
			'is_simple' => count($qs) == 1 && count($this->getVotes($qs[0]['quest'])) == 1,
		);

		return $res;
	}

	public function getQuestUnvotedVotes ($oQuest, $docId)
	{
		$res = array();
		$userVotes = $this->getUserVote(null, $oQuest->id, $docId);
		foreach ($oQuest->data['votes'] as $v) {
			$voteId= $v['vote_id'];
			if (!array_key_exists($voteId, $userVotes)) {
				$res[] = $v;
			}
		}
		return $res;
	} // end of method getQuestUnvotedVotes
}


